﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Threading;
using System.IO;

namespace EncoderFileWatcherApp
{

    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        FileSystemWatcher _fsw;

        Thread _worker;
        private int _secondsOfNoAccessToSignalDone = 15;
        //Lets say 20 K
        private long _minSizeForCompletedEncoding = 20480;

        bool _stopRequested = false;
        bool _exitRequested = false;
        private object _isProcessingLockObj = new object();
        private bool _isProcessing = false;
        public bool IsProcessing
        {
            get
            {
                lock (_isProcessingLockObj)
                {
                    return _isProcessing;
                }
            }
            set
            {
                lock (_isProcessingLockObj)
                {
                    _isProcessing = value;
                }
            }
        }


        public MainWindow()
        {
            InitializeComponent();
        }

        private void Grid_Loaded(object sender, RoutedEventArgs e)
        {
            FileConverter.ProgressEvent += new FileConverter.ProgressEventHandler(FileConverter_ProgressEvent);
            pathTextBox.Text = Properties.Settings.Default.DirectoryPath;
            profileTextBox.Text = Properties.Settings.Default.ProfileXmlFilePath;
        }

        private void startButton_Click(object sender, RoutedEventArgs e)
        {
            startButton.IsEnabled = false;
            Cursor = Cursors.Wait;
            try
            {
                stopButton.IsEnabled = true;
                stopAfterCurrentButton.IsEnabled = true;
                if (FileConverter.IsBusy)
                {
                    MessageBox.Show("FileConverter is already busy");
                }
                else if (this.IsProcessing)
                {
                    MessageBox.Show("Already processing the file list.");
                }
                else
                {
                    this._stopRequested = false;
                    //clear the queue
                    this.ClearFileQueue();

                    //start the file watcher
                    this.StartFileWatcher();

                    //Additionally detect the files that need processing
                    this.DetectFilesToProcess();

                    //Start the file processing loop
                    this.StartFileProcessingLoop();
                    this.currentProgressBar.Visibility = System.Windows.Visibility.Visible;
                }
                
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString());
                //we'll enable both again
                stopButton.IsEnabled = true;
                startButton.IsEnabled = true;
                stopAfterCurrentButton.IsEnabled = true;
            }
            finally
            {
                Cursor = Cursors.Arrow;
            }
        }

        private void stopButton_Click(object sender, RoutedEventArgs e)
        {
            stopButton.IsEnabled = false;
            stopAfterCurrentButton.IsEnabled = false;
            try
            {
                Cursor = Cursors.Wait;
                this.pleaseWaitTextBlock.Visibility = System.Windows.Visibility.Visible;
                Log("Attempting to stop...");
                this.currentProgressBar.Visibility = System.Windows.Visibility.Hidden;
                this._stopRequested = true;
                this.currentProgressLabel.Content = "Stop Requested...";
                string filename = FileConverter.Stop();
                //Wait 5 seconds or so for the 'job' to dispose.
                Thread.Sleep(5000);
                if (File.Exists(filename))
                {
                    //delete it
                    try
                    {
                        File.Delete(filename);
                    }
                    catch (Exception ex)
                    {
                        MessageBox.Show("Unable to delete " + filename + "\r\n" +
                            "It was not encoded completely.  Please be sure to delete that file manually.\r\n" + 
                            "(Error: " + ex.Message +")");
                    }
                }
                this.currentProgressLabel.Content = "Stopped";
                startButton.IsEnabled = true;
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString());
                //we'll enable both again
                stopButton.IsEnabled = true;
                stopAfterCurrentButton.IsEnabled = true;
                startButton.IsEnabled = true;

            }
            finally
            {
                this.pleaseWaitTextBlock.Visibility = System.Windows.Visibility.Hidden;
                Cursor = Cursors.Arrow;
            }
        }
        private void stopAfterCurrentButton_Click(object sender, RoutedEventArgs e)
        {
            stopButton.IsEnabled = false;
            stopAfterCurrentButton.IsEnabled = false;
            try
            {
                Cursor = Cursors.Wait;
                Log("Will stop after this job is complete...");
                //this.currentProgressBar.Visibility = System.Windows.Visibility.Hidden;
                this._stopRequested = true;
                this._exitRequested = true;
                this.filesToProcessLabel.Content += " - Will stop after current file is finished.";
                //FileConverter.Stop();
                //startButton.IsEnabled = true;
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString());
                //we'll enable both again
                stopButton.IsEnabled = true;
                stopAfterCurrentButton.IsEnabled = true;
                this._exitRequested = false;
                startButton.IsEnabled = true;
            }
            finally
            {

                Cursor = Cursors.Arrow;
            }

        }


        private bool StartFileProcessingLoop()
        {
            if (!_isProcessing)
            {
                //start the thread
                if (_worker != null)
                {
                    if (_worker.IsAlive)
                    {
                        MessageBox.Show("Worker thread is still alive.  Stop it before restarting it.");
                        return false;
                    }
                    else if (_worker.ThreadState == ThreadState.Running)
                    {
                        //cannot do!
                        MessageBox.Show("Worker thread is still running.  Stop it before restarting it.");
                        return false;
                    }
                    else
                    {
                        //It is not alive and not running.
                        //kill it with fire.
                        _worker = null;
                    }
                }
                ThreadStart ts = new ThreadStart(DoFileProcessingLoop);
                _worker = new Thread(ts);
                _worker.Start();
                return true;
            }
            else
            {
                return false;
            }
        }


        protected void ClearFileQueue()
        {
            this.filesToProcessListBox.Items.Clear();
        }

        protected void StartFileWatcher()
        {
            if (_fsw == null)
            {
                _fsw = new FileSystemWatcher(this.pathTextBox.Text, "*.avi");
                _fsw.Created += new FileSystemEventHandler(_fsw_Created);
                _fsw.Changed += new FileSystemEventHandler(_fsw_Changed);

            }
            else
            {
                //already connected.  Just update the path
                _fsw.Path = this.pathTextBox.Text;
            }
            //and start it
            this._fsw.EnableRaisingEvents = true;
        }

        void _fsw_Changed(object sender, FileSystemEventArgs e)
        {
            //Locate the file in the list and update the access time.
            this.UpdateLastAccessedTime(e.FullPath);
        }

        void _fsw_Created(object sender, FileSystemEventArgs e)
        {
            //it is new.  add it
            string fqn = e.FullPath;
            //add to the file list
            FileToProcess file = new FileToProcess();
            file.FileName = fqn;
            file.LastAccessed = DateTime.Now;
            file.ProgressPercent = 0;
            this.UpdateFileInList(file, true);

        }

        private delegate void UpdateFileInListHandler(FileToProcess file, bool triggerThreadToStart);
        protected void UpdateFileInList(FileToProcess file, bool triggerThreadToStart)
        {
            //update our progress bar
            if (this.filesToProcessListBox.Dispatcher.CheckAccess())
            {
                FileToProcess fileInList = FindFileInList(file.FileName);
                if (fileInList == null)
                {
                    this.Log("Adding " + file.FileName + " to list.");
                    this.filesToProcessListBox.Items.Add(file);
                    this.filesToProcessLabel.Content = "Files to process: " + this.filesToProcessListBox.Items.Count.ToString();

                }
                else
                {
                    //don't know why
                    fileInList.LastAccessed = DateTime.Now;
                }
                if (!_isProcessing && triggerThreadToStart)
                {
                    this.StartFileProcessingLoop();
                }

            }
            else
            {
                this.filesToProcessListBox.Dispatcher.BeginInvoke(new UpdateFileInListHandler(UpdateFileInList), new object[] { file, triggerThreadToStart });
            }
        }


        private delegate void UpdateLastAccessedTimeHandler(string fileName);
        protected void UpdateLastAccessedTime(string fileName)
        {

            //update our progress bar
            if (this.filesToProcessListBox.Dispatcher.CheckAccess())
            {
                FileToProcess fileInList = FindFileInList(fileName);
                if (fileInList == null)
                {
                    //not in the list?
                    //this.filesToProcessListBox.Items.Add(file);
                }
                else
                {
                    //don't know why
                    fileInList.LastAccessed = DateTime.Now;
                }
                if (!_isProcessing)
                {
                    this.StartFileProcessingLoop();
                }
            }
            else
            {
                this.filesToProcessListBox.Dispatcher.BeginInvoke(new UpdateLastAccessedTimeHandler(UpdateLastAccessedTime), new object[] { fileName });
            }
        }

        private delegate void FindFileInListHandler(string fileName);
        protected FileToProcess FindFileInList(string fileName)
        {
            FileToProcess file = null;

            if (this.filesToProcessListBox.Dispatcher.CheckAccess())
            {
                for (int i = 0; i < this.filesToProcessListBox.Items.Count; i++)
                {
                    if (((FileToProcess)this.filesToProcessListBox.Items[i]).FileName == fileName)
                    {
                        //found it.  return
                        file = (FileToProcess)this.filesToProcessListBox.Items[i];
                        break;
                    }
                }
                //if we drop through, return null
                //return null;

            }
            else
            {
                file = (FileToProcess)this.pathTextBox.Dispatcher.Invoke(new Func<FileToProcess>(() => FindFileInList(fileName)));
            }
            return file;
        }

        private delegate void RemoveFileFromListHandler(FileToProcess file);
        protected void RemoveFileFromList(FileToProcess file)
        {
            if (this.filesToProcessListBox.Dispatcher.CheckAccess())
            {
                this.filesToProcessListBox.Items.Remove(file);
                this.filesToProcessLabel.Content = "Files to process: " + this.filesToProcessListBox.Items.Count.ToString();
            }
            else
            {
                this.filesToProcessListBox.Dispatcher.BeginInvoke(new RemoveFileFromListHandler(RemoveFileFromList), new object[] { file });
            }
            
        }

        protected void StopFileWatcher()
        {
            this._fsw.EnableRaisingEvents = false;
        }

        protected void DetectFilesToProcess()
        {
            List<string> toProcess = new List<string>();
            //the aviFileName, does it have a .wmv counterpart?
            string[] files = Directory.GetFiles(this.pathTextBox.Text);
            for (int i = 0; i < files.Length; i++)
            {
                bool converted = false;
                string fileName = files[i];
                string extName = System.IO.Path.GetExtension(fileName);
                if (extName.ToLower() == ".avi")
                {
                    string dirName = System.IO.Path.GetFullPath(fileName);
                    string justName = System.IO.Path.GetFileNameWithoutExtension(fileName);
                    foreach (string candidate in files)
                    {
                        if (candidate.ToLower().Contains(justName + ".wmv"))
                        {
                            FileInfo fi = new FileInfo(candidate);
                            long len = fi.Length;
                            if (len > _minSizeForCompletedEncoding)
                            {
                                //found it
                                converted = true;
                                break;
                            }
                            else
                            {
                                //an orphan.  Delete it.
                                fi.Delete();
                            }
                        }
                    }
                    if (!converted)
                    {
                        toProcess.Add(files[i]);
                    }
                }
                else
                {
                    //skipping it
                }
            }

            this.Log("Detected " + toProcess.Count.ToString() + " files to process.");
            foreach (string s in toProcess)
            {
                FileToProcess f = new FileToProcess();
                f.FileName = s;
                f.ProgressPercent = 0;
                f.LastAccessed = DateTime.Now;
                //do not trigger the thread to start on the initial enumeration of files.
                this.UpdateFileInList(f, false);
            }
        }

        protected bool HasFileBeenConverted(string fileName)
        {
            bool converted = false;
            string justName = System.IO.Path.GetFileNameWithoutExtension(fileName);
                        
            //the aviFileName, does it have a .wmv counterpart?
            string[] files = Directory.GetFiles(this.pathTextBox.Text);
            if(files.Contains(justName + ".wmv"))
            {
                //found it
                converted = true;
            }            

            return converted;
        }



        protected void DoFileProcessingLoop()
        {
            if (!this.IsProcessing)
            {
                try
                {
                    this.Log("Beginning processing for files in list...");
                    this.IsProcessing = true;
                    //while (this.filesToProcessListBox.HasItems)
                    while (this.GetFileCount() > 0 && _stopRequested == false && _exitRequested == false)
                    {
                        FileToProcess file = null;
                        if (FileConverter.IsBusy)
                        {
                            //sleep a bit
                            Thread.Sleep(5000);
                        }
                        else
                        {
                            //for (int i = 0; i < this.filesToProcessListBox.Items.Count; i++)
                            bool shouldSkip = false;
                            if (_stopRequested || _exitRequested)
                            {
                                Log("Stopping before next file.");
                                break;
                            }
                            //do it.
                            //peel them off of the top.
                            //FileToProcess candidate = (FileToProcess)this.filesToProcessListBox.Items[i];
                            file = this.GetNextFileToProcess();


                            //--------------------------------------------------------
                            if (file == null)
                            {
                                //didn't find one.  Sleep a while
                                Thread.Sleep(5000);
                            }
                            else
                            {
                                if (!FileConverter.IsBusy)
                                {
                                    //FileConverter.ConvertFile(file.FileName, this.profileTextBox.Text, this.pathTextBox.Text);

                                    this.Log("Starting " + file.FileName);
                                    FileConverter.ConvertFile(file.FileName, this.GetProfile(), this.GetPath());
                                    this.RemoveFileFromList(file);
                                    this.SetFilePropertiesFromSourceToDestitination(file.FileName, ".wmv");
                                    this.Log("Finished encoding. Setting Created DateTime.");

                                }
                            }
                            //--------------------------------------------------------


                        }
                    }
                    if (this._exitRequested)
                    {
                        //exit the app
                        this.Log("Shutting down application...");
                        this.Quit();
                        //Application.Current.Shutdown();
                    }
                    else
                    {
                        this.Log("Awaiting next file(s) to process.");
                    }
                }
                catch (Exception ex)
                {
                    Log(ex.ToString());
                }
                finally
                {
                    //done, or exit.  
                    this.IsProcessing = false;
                }
            }
            else
            {
                //was already processing
            }
        }

 

        private delegate void QuitHandler();
        private void Quit()
        {
            //update our progress bar
            if (this.Dispatcher.CheckAccess())
            {
                this.StopFileWatcher();
                this.Close();
            }
            else
            {
                this.Dispatcher.BeginInvoke(new QuitHandler(Quit));
            }
        }

        void FileConverter_ProgressEvent(object sender, Microsoft.Expression.Encoder.EncodeProgressEventArgs e)
        {
            //update our progress bar
            if (this.currentProgressBar.Dispatcher.CheckAccess())
            {

                this.currentProgressBar.Value = e.Progress;
                string msg = String.Format("Working on {0} pass {1} of {2}", e.CurrentItem.SourceFileName, e.CurrentPass, e.TotalPasses);
                this.currentProgressLabel.Content = msg;
            }
            else
            {
                this.currentProgressBar.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal,
                   new EventHandler<Microsoft.Expression.Encoder.EncodeProgressEventArgs>(FileConverter_ProgressEvent), sender, new object[] { e });
            }
        }


        private string GetPath()
        {
            string path = "";
            if (this.pathTextBox.Dispatcher.CheckAccess())
            {
                path = this.pathTextBox.Text;
            }
            else
            {
                path = (string)this.pathTextBox.Dispatcher.Invoke(new Func<string>(() => this.pathTextBox.Text));
            }
            return path;
        }

        private string GetProfile()
        {
            string profile = "";
            if (this.profileTextBox.Dispatcher.CheckAccess())
            {
                profile = this.profileTextBox.Text;
            }
            else
            {
                profile = (string)this.profileTextBox.Dispatcher.Invoke(new Func<string>(() => this.profileTextBox.Text));
            }
            return profile;
        }

        private FileToProcess GetNextFileToProcess()
        {
            FileToProcess file = null;
            if (this.filesToProcessListBox.Dispatcher.CheckAccess())
            {
                List<FileToProcess> toRemove = new List<FileToProcess>();
                for(int i = 0; i < this.filesToProcessListBox.Items.Count; i++)
                {
                    FileToProcess candidate = (FileToProcess)this.filesToProcessListBox.Items[i];
                    //--------------------------------------------------------
                    if (candidate.LastAccessed < DateTime.Now.AddSeconds(-_secondsOfNoAccessToSignalDone))
                    {
                        if (File.Exists(candidate.FileName))
                        {
                            FileInfo fi = new FileInfo(candidate.FileName);
                            long len = fi.Length;
                            if (len > _minSizeForCompletedEncoding)
                            {
                                //found it
                                //use this one
                                file = candidate;
                                break;
                                //check again.
                            }
                            else
                            {
                                //empty file, possibly waiting to be filled by the camera encoder
                                //The Windows Live Video Importer will create several empty AVI files as containers
                                //for the scene-split video.  The files are placeholders, and sit empty for a while until
                                //the encoder gets around to filling them.  Don't try to start converting them
                                //until they are a) filled, and b) not access for a few seconds.

                                //leave the file null.  Keep looking.  
                            }
                        }
                        else
                        {
                            //where did the file go?  weird.  Remove it from our candidate list and
                            //move on to the next one.
                            this.Log("Unable to locate file " + file.FileName + ".  Removing from list.");
                            toRemove.Add(file);
                        }
                    }
                    else
                    {
                        //The candidate was access too recently.  
                        //Keep looking.  
                    }
                }
                //Do any cleanup.
                foreach (FileToProcess fileToRemove in toRemove)
                {
                    try
                    {
                        this.RemoveFileFromList(fileToRemove);
                    }
                    catch (Exception ex)
                    {
                        Log("Could not remove " + fileToRemove.FileName + " + from list. Error: " + ex.ToString());
                    }
                }
            }
            else
            {
                file = (FileToProcess)this.filesToProcessListBox.Dispatcher.Invoke(new Func<FileToProcess>(() => GetNextFileToProcess()));
            }
            return file;
        }

        private delegate int GetFileCountHandler();
        private int GetFileCount()
        {
            int count = 0;
            if (this.filesToProcessListBox.Dispatcher.CheckAccess())
            {
                count = this.filesToProcessListBox.Items.Count;
            }
            else
            {
                count = (int)this.filesToProcessListBox.Dispatcher.Invoke(new Func<int>(() => this.filesToProcessListBox.Items.Count));
            }
            return count;
        }

        private delegate void LogHandler(string msg);
        protected void Log(string msg)
        {
 
            if (this.logTextBox.Dispatcher.CheckAccess())
            {

                this.logTextBox.Items.Insert(0, DateTime.Now.ToLongTimeString() + "--" + msg);
                if (this.logTextBox.Items.Count > 300)
                {
                    while (this.logTextBox.Items.Count >= 300)
                    {
                        this.logTextBox.Items.RemoveAt(299);
                    }
                }
            }
            else
            {
                this.filesToProcessListBox.Dispatcher.BeginInvoke(new LogHandler(Log), new object[] { msg });
            }
        }

        private void helpButton_Click(object sender, RoutedEventArgs e)
        {
            MessageBox.Show("This app will convert AVI files to WMV files in the specified folders using the " +
                "Microsoft Expression Encoder 3 profile (xml) file specified.  When started, it will search the folder for AVI files " +
                "not having a WMV counterpart and queue them for encoding.  It will then monitor the folder for any new files created/modified " +
                "and will add them to the queue.", "About", MessageBoxButton.OK, MessageBoxImage.Information);

        }

        private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            if (_isProcessing)
            {
                this.stopButton_Click(sender, null);
            }
            e.Cancel = false;
        }

        private void setFilePropertiesButton_Click(object sender, RoutedEventArgs e)
        {
            Cursor = Cursors.Wait;
            try
            {
                Log("Setting .WMV file Created DateTimes to match their original .AVI files...");
                SetFilePropertiesFromSourceToDestitinationInDirectory(this.pathTextBox.Text, ".avi", ".wmv");
                Log("Finished setting properties.");
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString());
            }
            finally
            {
                Cursor = Cursors.Arrow;
            }
        }


        private void SetFilePropertiesFromSourceToDestitination(string srcFileName, string destExtension)
        {
            FileInfo fiSource = new FileInfo(srcFileName);

            string dirName = fiSource.Directory.FullName;
            string[] files = Directory.GetFiles(dirName);
            string thisExt = System.IO.Path.GetExtension(srcFileName);
            //it is the desired type for the source.  Keep going
            string justName = System.IO.Path.GetFileNameWithoutExtension(srcFileName);
            string candidate = dirName +"\\" + justName + destExtension;
            FileInfo fiDest = new FileInfo(candidate);
            long len = fiDest.Length;
            if (len > _minSizeForCompletedEncoding)
            {
                //found it
                this.SetFilePropertiesFromSourceToDestitination(fiSource, fiDest);
            }
        }

        public void SetFilePropertiesFromSourceToDestitinationInDirectory(string directory, string srcExtenstion, string destExtension)
        {
            //string[] files = Directory.GetFiles(directory, "*"+ srcExtenstion);
            //Need to get a list of ALL of the files for iterating
            string[] files = Directory.GetFiles(directory);
            foreach (string fileName in files)
            {
                FileInfo fiSource = new FileInfo(fileName);
                string thisExt = System.IO.Path.GetExtension(fileName);
                if (thisExt == srcExtenstion)
                {
                    //it is the desired type for the source.  Keep going
                    string justName = System.IO.Path.GetFileNameWithoutExtension(fileName);
                    string dirName = System.IO.Path.GetFullPath(fileName);
                    foreach (string candidate in files)
                    {
                        if (candidate.ToLower().Contains(justName + destExtension))
                        {
                            FileInfo fiDest = new FileInfo(candidate);
                            long len = fiDest.Length;
                            if (len > _minSizeForCompletedEncoding)
                            {
                                //found it
                                this.SetFilePropertiesFromSourceToDestitination(fiSource, fiDest);
                            }
                        }
                    }
                }
                else
                {
                    //it is not the desired file type for the source.
                    //Move on to the next
                }
            }
        }


        public void SetFilePropertiesFromSourceToDestitination(FileInfo src, FileInfo dest)
        {
            if (dest.CreationTime != src.CreationTime)
            {
                //only set it if it was different
                dest.CreationTime = src.CreationTime;
            }
        }

        private void exitMenuItem_Click(object sender, RoutedEventArgs e)
        {
            this.Close();
        }

        private void pathTextBox_LostFocus(object sender, RoutedEventArgs e)
        {
            string thePath = pathTextBox.Text;
            if (thePath.Length > 0)
            {
                if (System.IO.Directory.Exists(thePath))
                {
                    Properties.Settings.Default.DirectoryPath = pathTextBox.Text;
                    Properties.Settings.Default.Save();
                }
                else
                {
                    MessageBox.Show("The specified path '" + thePath + "' does not exist.");
                }
            }
            
        }


        private void profileTextBox_LostFocus(object sender, RoutedEventArgs e)
        {
            string thePath = profileTextBox.Text;
            if (thePath.Length > 0)
            {
                if (System.IO.File.Exists(thePath))
                {
                    Properties.Settings.Default.ProfileXmlFilePath = thePath;
                    Properties.Settings.Default.Save();
                }
                else
                {
                    MessageBox.Show("The specified file '" + thePath + "' does not exist.");
                }
            }
        }
    }


    public class FileToProcess
    {
        public string FileName { get; set; }
        public long ProgressPercent { get; set; }
        public DateTime LastAccessed { get; set; }

        public FileToProcess()
        {
            FileName = "";
            ProgressPercent = 0;
            LastAccessed = DateTime.MinValue;
        }
        public override string ToString()
        {
            return FileName;
        }
    }

}
